/**
 * Copyright (C) 2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.jatl;

import java.awt.Color;
import java.io.Writer;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;

/**
 * Most of the XHTML tags and attributes are available as methods. A rule of
 * thumb is that most tags are zero parameter methods and attribute methods are
 * single parameter.
 * 
 * @author adamgent
 * 
 * @param <T>
 *            See {@link MarkupBuilder} for how this should be parameterized.
 */
public abstract class HtmlBuilder<T> extends GeneratedHtmlBuilder<T> {

	public HtmlBuilder(Writer writer) {
		super(writer);
	}

	public HtmlBuilder(MarkupBuilder<?> builder) {
		super(builder);
	}

	public HtmlBuilder(MarkupBuilder<?> builder, boolean nested) {
		super(builder, nested);
	}

	protected HtmlBuilder() {
		super();
	}

	public T accept(String mediaType1, String mediaType2, String... mediaTypes) {
		return accept(join(',', mediaType1, mediaType2, mediaTypes));
	}

	public T acceptCharset(String charset1, String charset2, String... charsets) {
		return acceptCharset(join(' ', charset1, charset2, charsets));
	}

	public T accesskey(char key) {
		return accesskey("" + key);
	}

	// all possible values for align attribute from different tags 
	public static enum Align {
		LEFT, CENTER, RIGHT, JUSTIFY, CHAR, TOP, BOTTOM, MIDDLE;
	}

	public T align(Align alignment) {
		return align(alignment.toString().toLowerCase());
	}

	public T alink(Color color) {
		return alink(getHexCode(color));
	}

	public T archive(URI... uris) {
		String[] strings = new String[uris.length];
		for (int i = 0; i < uris.length; i++) {
			strings[i] = uris[i].toASCIIString();
		}
		return archive(StringUtils.join(strings, ','));
	}

	public T axis(String axis1, String axis2, String... axes) {
		return axis(join(',', axis1, axis2, axes));
	}

	public T background(URI imageUri) {
		return background(imageUri.toASCIIString());
	}

	public T bgcolor(Color color) {
		return bgcolor(getHexCode(color));
	}

	public T border(int pixels) {
		return border("" + pixels);
	}

	public T cellpadding(Length length) {
		return cellpadding(length.toString());
	}

	public T cellspacing(Length length) {
		return cellspacing(length.toString());
	}

	public T charAttr(char value) {
		return charAttr("" + value);
	}

	public T charoff(Length length) {
		return charoff(length.toString());
	}

	public T checked() {
		return checked("checked");
	}

	public T checked(boolean checked) {
		return checked ? checked() : getSelf();
	}

	public T cite(URI source) {
		return cite(source.toASCIIString());
	}

	public T classAttr(String class1, String class2, String... classes) {
		return classAttr(join(' ', class1, class2, classes));
	}

	public T classid(URI implUri) {
		return classid(implUri.toASCIIString());
	}

	public static enum Clear {
		LEFT, ALL, RIGHT, NONE
	}

	public T clear(Clear value) {
		return clear(value.toString().toLowerCase());
	}

	public T codebase(URI basePath) {
		return codebase(basePath.toASCIIString());
	}

	public T color(Color color) {
		return color(getHexCode(color));
	}

	public T cols(MultiLength... widths) {
		String[] strings = new String[widths.length];
		for (int i = 0; i < widths.length; i++) {
			strings[i] = widths[i].toString();
		}
		return cols(StringUtils.join(strings, ','));
	}

	public T cols(int сolumns) {
		return cols("" + сolumns);
	}

	public T colspan(int columns) {
		return colspan("" + columns);
	}

	public T compact() {
		return compact("compact");
	}

	public T compact(boolean compact) {
		return compact ? compact() : getSelf();
	}

	// these two methods exist only for parameter name hints in IDE
	public T coords(Length leftX, Length topY, Length rightX, Length bottomY) {
		return coords(new Length[] { leftX, topY, rightX, bottomY });
	}

	public T coords(Length centerX, Length centerY, Length radius) {
		return coords(new Length[] { centerX, centerY, radius });
	}

	public T coords(Length... vertexCoords) {
		if (vertexCoords.length % 2 != 0 && vertexCoords.length != 3)
			throw new IllegalArgumentException(
					"Number of coordinates must be even or 3");
		return coords(StringUtils.join(vertexCoords, ','));
	}

	public T data(URI dataUri) {
		return data(dataUri.toASCIIString());
	}

	public T datetime(Date dateTime) {
		return datetime(dateTime, TimeZone.getDefault());
	}

	public T datetime(Calendar calendar) {
		return datetime(calendar.getTime(), calendar.getTimeZone());
	}

	public T datetime(Date dateTime, TimeZone tz) {
		return datetime(encodeDateTime(dateTime, tz));
	}

	public T declare() {
		return declare("declare");
	}

	public T declare(boolean declare) {
		return declare ? declare() : getSelf();
	}

	public T defer() {
		return defer("defer");
	}

	public T defer(boolean defer) {
		return defer ? defer() : getSelf();
	}

	public static enum Direction {
		LTR, RTL
	}

	public T dir(Direction direction) {
		return dir(direction.toString().toLowerCase());
	}

	public T disabled() {
		return disabled("disabled");
	}

	public T disabled(boolean disabled) {
		return disabled ? disabled() : getSelf();
	}

	public T face(String face1, String face2, String... faces) {
		return face(join(',', face1, face2, faces));
	}

	public static enum FrameSides {
		VOID, ABOVE, BELOW, HSIDES, LHS, RHS, VSIDES, BOX, BORDER
	}

	public T frame(FrameSides sides) {
		return frame(sides.toString().toLowerCase());
	}

	public T frameborder(boolean hasBorders) {
		return frameborder(hasBorders ? "1" : "0");
	}

	public T headers(String header1, String header2, String... headers) {
		return headers(join(',', header1, header2, headers));
	}

	public T height(Length height) {
		return height(height.toString());
	}

	public T href(URI uri) {
		return href(uri.toASCIIString());
	}

	public T hspace(int pixels) {
		return hspace("" + pixels);
	}

	public T ismap() {
		return ismap("ismap");
	}

	public T ismap(boolean ismap) {
		return ismap ? ismap() : getSelf();
	}

	public T link(Color color) {
		return link(getHexCode(color));
	}

	public T longdesc(URI uri) {
		return longdesc(uri.toASCIIString());
	}

	public T marginheight(int pixels) {
		return marginheight("" + pixels);
	}

	public T marginwidth(int pixels) {
		return marginwidth("" + pixels);
	}

	public T maxlength(int characters) {
		return maxlength("" + characters);
	}

	public T media(String medium1, String medium2, String... media) {
		return media(join(',', medium1, medium2, media));
	}

	public static enum Method {
		GET, POST
	}

	public T method(Method method) {
		return method(method.toString().toLowerCase());
	}

	public T multiple() {
		return multiple("multiple");
	}

	public T multiple(boolean multiple) {
		return multiple ? multiple() : getSelf();
	}

	public T nohref() {
		return nohref("nohref");
	}

	public T nohref(boolean nohref) {
		return nohref ? nohref() : getSelf();
	}

	// public T noresize() {
	// return noresize("noresize");
	// }
	//
	// public T noresize(boolean noresize) {
	// return noresize ? noresize() : getSelf();
	// }

	public T noshade() {
		return noshade("noshade");
	}

	public T noshade(boolean noshade) {
		return noshade ? noshade() : getSelf();
	}

	public T nowrap() {
		return nowrap("nowrap");
	}

	public T nowrap(boolean nowrap) {
		return nowrap ? nowrap() : getSelf();
	}

	public T profile(URI... uris) {
		String[] strings = new String[uris.length];
		for (int i = 0; i < uris.length; i++) {
			strings[i] = uris[i].toASCIIString();
		}
		return profile(StringUtils.join(strings, ' '));
	}

	public T readonly() {
		return readonly("readonly");
	}

	public T readonly(boolean readonly) {
		return readonly ? readonly() : getSelf();
	}

	public T rel(String linkType1, String linkType2, String... linkTypes) {
		return rel(join(' ', linkType1, linkType2, linkTypes));
	}

	public T rev(String linkType1, String linkType2, String... linkTypes) {
		return rev(join(' ', linkType1, linkType2, linkTypes));
	}

	public T rows(MultiLength... heights) {
		String[] strings = new String[heights.length];
		for (int i = 0; i < heights.length; i++) {
			strings[i] = heights[i].toString();
		}
		return rows(StringUtils.join(strings, ','));
	}

	public T rows(int rows) {
		return rows("" + rows);
	}

	public T rowspan(int rowspan) {
		return rowspan("" + rowspan);
	}

	public static enum Rules {
		NONE, GROUPS, ROWS, COLS, ALL
	}

	public T rules(Rules rules) {
		return rules(rules.toString().toLowerCase());
	}

	public static enum Scope {
		ROW, COL, ROWGROUP, COLGROUP
	}

	public T scope(Scope scope) {
		return scope(scope.toString().toLowerCase());
	}

	public static enum Scrolling {
		YES, NO, AUTO
	}

	public T scrolling(Scrolling scrolling) {
		return scrolling(scrolling.toString().toLowerCase());
	}

	public T selected() {
		return selected("selected");
	}

	public T selected(boolean selected) {
		return selected ? selected() : getSelf();
	}

	public static enum Shape {
		DEFAULT, RECT, CIRCLE, POLY
	}

	public T shape(Shape shape) {
		return shape(shape.toString().toLowerCase());
	}

	public T size(int size) {
		return size("" + size);
	}

	public T sizeIncrease(int factor) {
		return size("+" + factor);
	}

	public T sizeDecrease(int factor) {
		return size("-" + factor);
	}

	public T span(int span) {
		return span("" + span);
	}

	public T src(URI uri) {
		return src(uri.toASCIIString());
	}

	public T start(int index) {
		return start("" + index);
	}

	public T tabindex(int index) {
		return tabindex("" + index);
	}

	public T text(Color color) {
		return text(getHexCode(color));
	}

	public static enum InputType {
		TEXT, PASSWORD, CHECKBOX, RADIO, SUBMIT, RESET, FILE, HIDDEN, IMAGE, BUTTON
	}

	public T type(InputType inputType) {
		return type(inputType.toString().toLowerCase());
	}

	public static enum ListStyle {
		NUMBERS {
			@Override
			public String toString() {
				return "1";
			}
		},
		LOWER_CASE_ALPHABETIC {
			@Override
			public String toString() {
				return "a";
			}
		},
		UPPER_CASE_ALPHABETIC {
			@Override
			public String toString() {
				return "A";
			}
		},
		LOWER_CASE_ROMAN {
			@Override
			public String toString() {
				return "i";
			}
		},
		UPPER_CASE_ROMAN {
			@Override
			public String toString() {
				return "I";
			}
		},
		DISK {
			@Override
			public String toString() {
				return "disc";
			}
		},
		SQUARE {
			@Override
			public String toString() {
				return "square";
			}
		},
		CIRCLE {
			@Override
			public String toString() {
				return "circle";
			}
		}
	}

	public T type(ListStyle listStyle) {
		return type(listStyle.toString());
	}

	public T usemap(URI uri) {
		return usemap(uri.toASCIIString());
	}

	public static enum VAlign {
		TOP, MIDDLE, BOTTOM, BASELINE
	}

	public T valign(VAlign alignment) {
		return valign(alignment.toString().toLowerCase());
	}

	public T value(int value) {
		return value("" + value);
	}

	public static enum ValueType {
		DATA, REF, OBJECT
	}

	public T valuetype(ValueType valueType) {
		return valuetype(valueType.toString().toLowerCase());
	}

	public T vlink(Color color) {
		return vlink(getHexCode(color));
	}

	public T vspace(int pixels) {
		return vspace("" + pixels);
	}

	public T width(MultiLength multiLength) {
		return width(multiLength.toString());
	}

	public T width(int widthHint) {
		return width("" + widthHint);
	}

	public T checkbox() {
		return checkbox(false);
	}

	public T checkbox(boolean checked) {
		input();
		type(InputType.CHECKBOX);
		checked(checked);
		return getSelf();
	}

	public T option(String name, String value, boolean selected) {
		option();
		value(value);
		selected(selected);
		text(name);
		end();
		return getSelf();
	}

	@Override
	protected String escapeMarkup(String raw) {
		return StringEscapeUtils.escapeHtml(raw);
	}

	/**
	 * Generates HTML5 custom attribute
	 */
	public T dataAttr(String name, String value) {
		return attr("data-" + name, value);
	}

	public static interface MultiLength {
	}

	public static interface Length extends MultiLength {
	}

	public static MultiLength relative(final int i) {
		return new MultiLength() {
			@Override
			public String toString() {
				return i + "*";
			}
		};
	}

	public static Length pixels(final int i) {
		return new Length() {
			@Override
			public String toString() {
				return "" + i;
			}
		};
	}

	public static Length px(int i) {
		return pixels(i);
	}

	public static Length percent(final int i) {
		return new Length() {
			@Override
			public String toString() {
				return i + "%";
			}
		};
	}

	public static interface FrameTarget {
		static String BLANK = "_blank";
		static String SELF = "_self";
		static String PARENT = "_parent";
		static String TOP = "_top";
	}

	public static interface LinkType {
		static String ALTERNATE = "alternate";
		static String STYLESHEET = "stylesheet";
		static String START = "start";
		static String NEXT = "next";
		static String PREV = "prev";
		static String CONTENTS = "contents";
		static String INDEX = "index";
		static String GLOSSARY = "glossary";
		static String COPYRIGHT = "copyright";
		static String CHAPTER = "chapter";
		static String SECTION = "section";
		static String SUBSECTION = "subsection";
		static String APPENDIX = "appendix";
		static String HELP = "help";
		static String BOOKMARK = "bookmark";
	}

	private String join(char separator, String str1, String str2,
			String[] strings) {
		String[] array = new String[strings.length + 2];
		array[0] = str1;
		array[1] = str2;
		System.arraycopy(strings, 0, array, 2, strings.length);
		return StringUtils.join(array, separator);
	}

	private String getHexCode(Color color) {
		int rgbWithoutAlpha = color.getRGB() & 0xFFFFFF;
		String hexString = Integer.toHexString(rgbWithoutAlpha);
		return "#" + StringUtils.leftPad(hexString, 6, '0');
	}

	private String encodeDateTime(Date dateTime, TimeZone tz) {
		SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZZ");
		dtf.setTimeZone(tz);

		String formattedWithoutColon = dtf.format(dateTime);
		return new StringBuilder(formattedWithoutColon).insert(
				formattedWithoutColon.length() - 2, ':').toString();
	}
}
